package com.gitee.wsl.doc.prosemirror.state.command

import com.gitee.wsl.doc.prosemirror.model.content.ResolvedPos
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.node.NodeBase
import com.gitee.wsl.doc.prosemirror.model.node.NodeType
import com.gitee.wsl.doc.prosemirror.model.node.ext.canSplit
import com.gitee.wsl.doc.prosemirror.state.Command
import com.gitee.wsl.doc.prosemirror.state.CommandDispatch
import com.gitee.wsl.doc.prosemirror.state.EditorState
import com.gitee.wsl.doc.prosemirror.state.Transaction
import com.gitee.wsl.doc.prosemirror.state.selection.AllSelection
import com.gitee.wsl.doc.prosemirror.state.selection.NodeSelection
import com.gitee.wsl.doc.prosemirror.state.selection.TextSelection
import com.gitee.wsl.doc.prosemirror.transform.ext.setNodeMarkup
import com.gitee.wsl.doc.prosemirror.transform.ext.split

class SplitBlockAs(
    val splitNode :((node: Node, atEnd: Boolean, from: ResolvedPos) -> NodeBase?) ?= null
): Command {
    override fun exec(
        state: EditorState,
        dispatch: CommandDispatch?,
    ): Boolean {
        val selection = state.selection
        val (from, to) = when (selection) {
            is TextSelection -> selection._from to selection._to
            else -> return false
        }

        // 处理节点选择情况
        if (selection is NodeSelection && selection.node.isBlock) {
            if ( from.parentOffset >= 0 || !state.doc.canSplit(from.pos)) return false
            dispatch?.invoke(state.tr.split(from.pos).scrollIntoView())
            return true
        }

        if (from.depth < 0) return false

        val types = mutableListOf<NodeBase?>()
        var splitDepth = 0
        var deflt: NodeType? = null
        var atEnd = false
        var atStart = false

        // 向上遍历寻找块级节点
        for (d in from.depth downTo 1) {
            val node = from.node(d)
            if (node.isBlock) {
                atEnd = from.end(d) == from.pos + (from.depth - d)
                atStart = from.start(d) == from.pos - (from.depth - d)
                deflt = defaultBlockAt(from.node(d - 1).contentMatchAt(from.indexAfter(d - 1)))

                val splitType = splitNode?.invoke(to.parent, atEnd, from)
                val element = splitType ?: if (atEnd && deflt != null) NodeBase(deflt, null) else null
                types.add(0, element)
                splitDepth = d
                break
            } else {
                if (d == 1) return false
                types.add(0, null)
            }
        }

        var tr = state.tr
        if (selection is TextSelection || selection is AllSelection) {
            tr = tr.deleteSelection()
        }

        val splitPos = tr.mapping.map(from.pos)
        var can = tr.doc.canSplit( splitPos, types.size, types)

        if (!can && deflt != null) {
            types[0] = NodeBase(deflt, null)
            can = tr.doc.canSplit( splitPos, types.size, types)
        }

        if (!can) return false

        tr.split(splitPos, types.size, types)

        // 处理分割边界情况
        if (!atEnd && atStart && from.node(splitDepth).type != deflt) {
            val first = tr.mapping.map(from.before(splitDepth))
            val firstPos = tr.doc.resolve(first)
            if (deflt != null && from.node(splitDepth - 1).canReplaceWith(firstPos.index(), firstPos.index() + 1, deflt)) {
                tr.setNodeMarkup(tr.mapping.map(from.before(splitDepth)), deflt)
            }
        }

        dispatch?.invoke(tr.scrollIntoView())
        return true
    }
}

val Command.Companion.splitBlock get() = SplitBlockAs()

fun Command.Companion.splitBlock(
    splitNode :(node: Node, atEnd: Boolean, from: ResolvedPos) -> NodeBase?
) = SplitBlockAs(splitNode)